﻿#include <string.h>
#include <stdlib.h>
#include <stdio.h>

#include <glib.h>
#include <gio/gio.h>
#include <libsoup/soup.h>
#include "jsonrpc.h"

#include "jsonrpc_server.h"

#include "util.h"
#include "db.h"

#ifdef G_OS_WIN32
#include "win32.h"
#endif

json_object *ajax(json_object *params) {
	const char *url = json_object_get_string(json_object_object_get(params, "url"));
	json_object *_method = json_object_object_get(params, "method");
	const char *method = _method ? json_object_get_string(_method) : SOUP_METHOD_GET;
	json_object *headers = json_object_object_get(params, "headers");
	json_object *data = json_object_object_get(params, "data");
	json_object *cmd = json_object_object_get(params, "ext");

	GString *data_string = NULL;
	if (data) {
		data_string = g_string_new(NULL );

		json_object_object_foreach(data, key, val) {
			switch (json_object_get_type(val)) {
			case json_type_null:
			case json_type_boolean:
			case json_type_double:
			case json_type_int:
			case json_type_string: {
				g_string_append_printf(data_string, "%s=%s&", key, json_object_get_string(val));
				break;
			}
			case json_type_object:
			case json_type_array: {
				break;
			}
			default:
				break;
			}
		}
		if (data_string->len > 0) {
			data_string->str[data_string->len - 1] = '\0';
		}
	}

	SoupSession *session = soup_session_sync_new();

	GString *_url = NULL;
	if (data_string && g_strcasecmp(method, SOUP_METHOD_GET) == 0) {
		_url = g_string_new(NULL );
		g_string_printf(_url, "%s%s%s", url, strchr(url, '?') ? "&" : "?", data_string->str);
	} else {
		_url = g_string_new(url);
	}
	SoupMessage *msg = soup_message_new(method, _url->str);
	g_string_free(_url, TRUE );

	if (headers) {
		json_object_object_foreach(headers, key, val)
		{
			soup_message_headers_append(msg->request_headers, key, json_object_get_string(val));
		}
	}

	if (data_string) {
		if (g_strcasecmp(method, SOUP_METHOD_POST) == 0) {
			soup_message_body_append(msg->request_body, SOUP_MEMORY_COPY, data_string->str, data_string->len);
		}
		g_string_free(data_string, TRUE);
	}

	soup_session_send_message(session, msg);

	json_object *response = json_object_new_object();

	if (cmd) {
		const char *cmdname = json_object_get_string(json_object_object_get(cmd, "name"));
		if (g_strcasecmp(cmdname, "save") == 0) {
			const char *filename = json_object_get_string(json_object_object_get(cmd, "filename"));
			json_object *minsize = json_object_object_get(cmd, "minsize");
			gboolean ret = 0;
			if (!minsize || (minsize && msg->response_body->length >= json_object_get_int(minsize))) {
				ret = g_file_set_contents(filename, msg->response_body->data, msg->response_body->length, NULL );
			}
			json_object_object_add(response, "retval", json_object_new_int(ret));
		}
	} else {
		gchar *statusline = g_strdup_printf("HTTP/1.%d %d %s", soup_message_get_http_version(msg), msg->status_code,
				msg->reason_phrase);
		json_object_object_add(response, "statusline", json_object_new_string(statusline));
		g_free(statusline);

		json_object *response_headers = json_object_new_object();
		const char *name, *value;
		SoupMessageHeadersIter iter;
		soup_message_headers_iter_init(&iter, msg->response_headers);
		while (soup_message_headers_iter_next(&iter, &name, &value)) {
			if (g_strcasecmp(name, "Set-Cookie") == 0) {
				json_object *cookies = json_object_object_get(response_headers, "Set-Cookie");
				if (!cookies) {
					cookies = json_object_new_array();
					json_object_object_add(response_headers, name, cookies);
				}
				json_object_array_add(cookies, json_object_new_string(value));
			} else {
				json_object_object_add(response_headers, name, json_object_new_string(value));
			}
		}
		json_object_object_add(response, "headers", response_headers);

		const char *from = json_object_get_string(json_object_object_get(params, "from"));
		const char *to = json_object_get_string(json_object_object_get(params, "to"));
		if (from && to) {
			gsize size = 0;
			char *str = g_convert(msg->response_body->data, msg->response_body->length, to, from, &size, NULL, NULL );
			json_object_object_add(response, "body", json_object_new_string(str));
			g_free(str);
		} else {
			json_object_object_add(response, "body",
					json_object_new_string_len(msg->response_body->data, msg->response_body->length));
		}
	}

	g_object_unref(msg);
	soup_session_abort(session);
	g_object_unref(session);

	return response;
}

static json_object *json_file(int type, const char *filename, goffset size, const char *modifiedtime) {
	json_object *f = json_object_new_object();
	json_object_object_add(f, "type", json_object_new_int(type));
	json_object_object_add(f, "name", json_object_new_string(filename));
	json_object_object_add(f, "size", json_object_new_int64(size));
	json_object_object_add(f, "modifiedtime", json_object_new_string(modifiedtime));
	return f;
}

json_object *files(json_object *params) {
	const char *path = json_object_get_string(json_object_object_get(params, "path"));
	json_object *type = json_object_object_get(params, "type");
	GFileType filetype = type ? json_object_get_int(type) : -1;
	GDir *dir = NULL;
	const char *filename;
	json_object *ret = json_object_new_array();

	if (path && strlen(path) > 0) {
		dir = g_dir_open(path, 0, NULL );
		if (!dir) {
			return ret;
		}

		while ((filename = g_dir_read_name(dir)) != NULL ) {
			char *abs_path = g_build_filename(path, filename, NULL );

			GFile *file = g_file_new_for_path(abs_path);
			GFileInfo *info = g_file_query_info(file, "*", G_FILE_QUERY_INFO_NONE, NULL, NULL );
			GFileType type = g_file_info_get_file_type(info);
			goffset size = g_file_info_get_size(info);
			GTimeVal val;
			g_file_info_get_modification_time(info, &val);
			GDateTime *datetime = g_date_time_new_from_timeval_local(&val);
			gchar *modifiedtime = g_date_time_format(datetime, "%Y-%m-%d %H:%M:%S");
			if (filetype == -1 || filetype == type) {
				json_object_array_add(ret, json_file(type, filename, size, modifiedtime));
			}
			g_free(modifiedtime);
			g_date_time_unref(datetime);
			g_object_unref(info);
			g_object_unref(file);
			g_free(abs_path);
		}
		g_dir_close(dir);
	} else {
		GVolumeMonitor *monitor = g_volume_monitor_get();
		GList *mounts = g_volume_monitor_get_mounts(monitor);
		g_object_unref(monitor);
		for (GList *iterator = mounts; iterator; iterator = iterator->next) {
			GMount *mount = (GMount *) iterator->data;
			GFile *root = g_mount_get_root(mount);
			char *path = g_file_get_path(root);
			json_object_array_add(ret, json_file(G_FILE_TYPE_DIRECTORY, path, 0, ""));
			g_object_unref(root);
			g_free(path);
		}
		g_list_free(mounts);
	}

	return ret;
}

json_object *base64(json_object *params) {
	const char *text = json_object_get_string(json_object_object_get(params, "text"));
	/* Text source(0: text, 1: file, 2: url) */
	int type = json_object_get_int(json_object_object_get(params, "type"));
	json_object *encode = json_object_object_get(params, "encode");
	gboolean is_encode = encode ? json_object_get_boolean(encode) : TRUE;
	GString *buf = NULL;

	switch (type) {
	case 0: {
		if (is_encode) {
			gchar *str = g_base64_encode((const guchar *) text, strlen(text));
			buf = g_string_new(str);
			g_free(str);
		} else {
			gsize size = 0;
			guchar *str = g_base64_decode(text, &size);
			buf = g_string_new_len((gchar *)str, size);
			g_free(str);
		}
		break;
	}

	case 1: {
		gchar *contents = NULL;
		gsize length;

		if (g_file_get_contents(text, &contents, &length, NULL )) {
			if (is_encode) {
				gchar *str = g_base64_encode((const guchar *) contents, length);
				buf = g_string_new(str);
				g_free(str);
				g_free(contents);
			} else {
				gsize size = 0;
				guchar *str = g_base64_decode(contents, &size);
				buf = g_string_new_len((gchar *)str, size);
				g_free(str);
			}
		}
		break;
	}

	case 2: {
		SoupSession *session = soup_session_sync_new();
		SoupMessage *msg = soup_message_new(SOUP_METHOD_GET, text);
		soup_session_send_message(session, msg);
		if (is_encode) {
			gchar *str = g_base64_encode((const guchar *) msg->response_body->data, msg->response_body->length);
			buf = g_string_new(str);
			g_free(str);
		} else {
			gsize size = 0;
			guchar *str = g_base64_decode(msg->response_body->data, &size);
			buf = g_string_new_len((gchar *)str, size);
			g_free(str);
		}
		g_object_unref(msg);
		soup_session_abort(session);
		g_object_unref(session);
		break;
	}

	default:
		break;
	}

	json_object *response = NULL;

	if (buf) {
		response = json_object_new_string_len(buf->str, buf->len);
		g_string_free(buf, TRUE);
	}

	return response;
}

json_object *cmd(json_object *params) {
	const char *text = json_object_get_string(params);
	GString *result = shell(text);
	json_object *response = json_object_new_string(result->str);
	g_string_free(result, TRUE);
	return response;
}

void jsonrpc_server_destroy(jsonrpc_server *server) {
	g_hash_table_destroy(server->methods);
}

jsonrpc_server *jsonrpc_server_new() {
	jsonrpc_server *server = g_new0(jsonrpc_server, 1);
	server->methods = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL );

	jsonrpc_register_method(server, "ajax", ajax);
	jsonrpc_register_method(server, "files", files);
	jsonrpc_register_method(server, "base64", base64);
	jsonrpc_register_method(server, "sqlite", sqlite_exec);
	jsonrpc_register_method(server, "cmd", cmd);

#ifdef G_OS_WIN32
	jsonrpc_register_method(server, "script", script);
#endif

	return server;
}
